﻿using System;
using System.Collections.Generic;
using NN = NNCore;

namespace Controller
{
	public class ReversiController
	{

		private AbstractPlayer player1;
		private AbstractPlayer player2;
		private AbstractPlayer activePlayer;

		private NN.Controller nnController1;
		private NN.Controller nnController2;

		private bool timerEnabled;

		private double timerInterval;
		public double TimerInterval
		{
			get { return timerInterval; }
			set
			{
				timerInterval = value;
				player1.TimerInterval = timerInterval;
				player2.TimerInterval = timerInterval;
			}
		}

		private Token[,] board;
		private Index[] possibleMoves;

		public Index[] PossibleMoves
		{
			get { return possibleMoves; }
		}

		private bool isRunning = false;

		public bool IsRunning
		{
			get { return isRunning; }
		}

		public ReversiController(PlayerTypes p1, PlayerTypes p2, NN.Controller nnController1 = null, NN.Controller nnController2 = null, int id1 = 0, int id2 = 1, bool timerEnabled = true, double timerInterval = 1500)
		{
			this.timerEnabled = timerEnabled;
			this.timerInterval = timerInterval;
			this.nnController1 = nnController1;
			this.nnController2 = nnController2;
			InitGame(p1, p2, id1, id2);
		}

		public void StartGame()
		{
			activePlayer = player1;
			isRunning = true;
			possibleMoves = GetPossibleMoves();
			OnTokenPlaced(null);
			OnStatusChanged("Spiel läuft", "Spieler 1 ist an der Reihe");
			activePlayer.StartMove(ref this.possibleMoves);
		}

		#region Initialization

		private void InitGame(PlayerTypes p1, PlayerTypes p2, int id1, int id2)
		{
			InitBoard();
			InitPlayers(p1, id1, p2, id2);
		}

		private void InitBoard()
		{
			board = new Token[8, 8];
			for (int i = 0; i < board.GetLength(0); i++)
			{
				for (int j = 0; j < board.GetLength(1); j++)
				{
					if ((i == 3 && j == 3) || (i == 4 && j == 4))
					{
						board[i, j] = Token.Player1;
					}
					else if ((i == 4 && j == 3) || (i == 3 && j == 4))
					{
						board[i, j] = Token.Player2;
					}
					else
					{
						board[i, j] = Token.Default;
					}
				}
			}
		}

		private void InitPlayers(PlayerTypes p1, int id1, PlayerTypes p2, int id2)
		{
			SetPlayer(p1, out player1, id1, Token.Player1, nnController1);
			SetPlayer(p2, out player2, id2, Token.Player2, nnController2);
		}

		private void SetPlayer(PlayerTypes playerType, out AbstractPlayer player, int id, Token token, NN.Controller nnController)
		{
			switch (playerType)
			{
				case PlayerTypes.Player:
					player = new PlayerHuman(id, token);
					break;
				case PlayerTypes.Computer:
					player = new PlayerComputer(id, token, this, timerEnabled, TimerInterval);
					break;
				case PlayerTypes.KI:
					player = new PlayerSmartComputer(id, token, this, timerEnabled, TimerInterval);
					break;
				case PlayerTypes.NN:
					player = new PlayerNN(id, token, this, timerEnabled, TimerInterval, nnController);
					break;
				default:
					player = null;
					break;
			}
		}

		#endregion

		#region Work

		private Index[] GetPossibleMoves()
		{
			List<Index> possibleMoves = new List<Index>();
			for (int column = 0; column < board.GetLength(0); column++)
			{
				for (int row = 0; row < board.GetLength(1); row++)
				{
					if (board[column, row] == Token.Default
						&& (CheckUp(board, column, row) != -1 || CheckDown(board, column, row) != -1
						|| CheckLeft(board, column, row) != -1 || CheckRight(board, column, row) != -1
						|| CheckLeftUp(board, column, row) != -1 || CheckLeftDown(board, column, row) != -1
						|| CheckRightUp(board, column, row) != -1 || CheckRightDown(board, column, row) != -1))

						possibleMoves.Add(new Index(column, row));
				}
			}
			activePlayer.CountPossibleMoves += possibleMoves.Count;
			return possibleMoves.ToArray();
		}

		public bool PlaceToken(int column, int row, Type player)
		{
			if (isRunning && player.Equals(activePlayer.GetType()) && MakeMove(ref board, column, row))
			{
				OnTokenPlaced(new Index(column, row));
				ChangePlayer();
				return true;
			}
			return false;
		}

		public Token[,] GetBoardAfterPlaceToken(int column, int row)
		{
			Token[,] retBoard = new Token[board.GetLength(0), board.GetLength(1)];
			for (int columnIdx = 0; columnIdx < board.GetLength(0); columnIdx++)
			{
				for (int rowIdx = 0; rowIdx < board.GetLength(1); rowIdx++)
				{
					retBoard[columnIdx, rowIdx] = board[columnIdx, rowIdx];
				}
			}
			MakeMove(ref retBoard, column, row);
			return retBoard;
		}

		#region Check

		private bool CheckAll(int column, int row)
		{
			return (board[column, row] == Token.Default
						&& (CheckUp(board, column, row) != -1 || CheckDown(board, column, row) != -1
						|| CheckLeft(board, column, row) != -1 || CheckRight(board, column, row) != -1
						|| CheckLeftUp(board, column, row) != -1 || CheckLeftDown(board, column, row) != -1
						|| CheckRightUp(board, column, row) != -1 || CheckRightDown(board, column, row) != -1));
		}

		private bool MakeMove(ref Token[,] board, int column, int row)
		{
			bool isTokenPlaced = false;

			if (board[column, row] != Token.Default)
			{
				return isTokenPlaced;
			}

			int value = CheckRight(board, column, row);
			if (value != -1)
			{
				SetTokenHorizontal(ref board, column, value, row);
				isTokenPlaced = true;
			}
			value = CheckLeft(board, column, row);
			if (value != -1)
			{
				SetTokenHorizontal(ref board, column, value, row);
				isTokenPlaced = true;
			}
			value = CheckUp(board, column, row);
			if (value != -1)
			{
				SetTokenVertical(ref board, row, value, column);
				isTokenPlaced = true;
			}
			value = CheckDown(board, column, row);
			if (value != -1)
			{
				SetTokenVertical(ref board, row, value, column);
				isTokenPlaced = true;
			}
			value = CheckLeftUp(board, column, row);
			if (value != -1)
			{
				SetTokenRightDown(ref board, column, value, row);
				isTokenPlaced = true;
			}
			value = CheckLeftDown(board, column, row);
			if (value != -1)
			{
				SetTokenLeftDown(ref board, column, value, row);
				isTokenPlaced = true;
			}
			value = CheckRightUp(board, column, row);
			if (value != -1)
			{
				SetTokenLeftDown(ref board, column, value, row);
				isTokenPlaced = true;
			}
			value = CheckRightDown(board, column, row);
			if (value != -1)
			{
				SetTokenRightDown(ref board, column, value, row);
				isTokenPlaced = true;
			}
			return isTokenPlaced;
		}

		/// <summary>
		/// Sets the tokens of the active Player horizontally
		/// </summary>
		/// <param name="column1">The column where the new token was set.</param>
		/// <param name="column2">The column where the next token of the active player is.</param>
		/// <param name="row">The row where the new token was set.</param>
		private void SetTokenHorizontal(ref Token[,] board, int column1, int column2, int row)
		{
			int start = column1 > column2 ? column2 : column1;
			int ende = column1 > column2 ? column1 : column2;

			for (int column = start; column <= ende; column++)
			{
				board[column, row] = activePlayer.Token;
			}
		}

		/// <summary>
		/// Sets the tokens of the active Player vertically
		/// </summary>
		/// <param name="row1">The row where the new token was set.</param>
		/// <param name="row2">The row where the next token of the active player is.</param>
		/// <param name="column">The column where the new token was set.</param>
		private void SetTokenVertical(ref Token[,] board, int row1, int row2, int column)
		{
			int start = row1 > row2 ? row2 : row1;
			int ende = row1 > row2 ? row1 : row2;

			for (int row = start; row <= ende; row++)
			{
				board[column, row] = activePlayer.Token;
			}
		}

		/// <summary>
		/// Sets the tokens of the active Player diagonally from up left to right down
		/// </summary>
		/// <param name="column1">The column where the new token was set.</param>
		/// <param name="column2">The column where the next token of the active player is.</param>
		/// <param name="row">The row where the new token was set.</param>
		private void SetTokenRightDown(ref Token[,] board, int column1, int column2, int row)
		{

			int start = column1 > column2 ? column2 : column1;
			int ende = column1 > column2 ? column1 : column2;
			if (start != column1)
			{
				row -= (column1 - column2);
			}
			for (int column = start; column <= ende; column++, row++)
			{
				board[column, row] = activePlayer.Token;
			}
		}

		/// <summary>
		/// Sets the tokens of the active Player diagonally from up right to left down
		/// </summary>
		/// <param name="column1">The column where the new token was set.</param>
		/// <param name="column2">The column where the next token of the active player is.</param>
		/// <param name="row">The row where the new token was set.</param>
		private void SetTokenLeftDown(ref Token[,] board, int column1, int column2, int row)
		{

			int start = column1 > column2 ? column2 : column1;
			int ende = column1 > column2 ? column1 : column2;
			if (start != column1)
			{
				row += (column1 - column2);
			}
			for (int column = start; column <= ende; column++, row--)
			{
				board[column, row] = activePlayer.Token;
			}
		}

		private int CheckRight(Token[,] board, int column, int row)
		{
			if (column != board.GetLength(0) - 1
				&& board[column + 1, row] != activePlayer.Token
				&& board[column + 1, row] != Token.Default)
			{
				for (int x = column + 2; x < board.GetLength(0); x++)
				{
					if (board[x, row] == activePlayer.Token)
						return x;
					else if (board[x, row] == Token.Default)
						return -1;
				}
			}
			return -1;
		}

		private int CheckLeft(Token[,] board, int column, int row)
		{
			if (column != 0
				&& board[column - 1, row] != activePlayer.Token
				&& board[column - 1, row] != Token.Default)
			{
				for (int x = column - 2; x >= 0; x--)
				{
					if (board[x, row] == activePlayer.Token)
						return x;
					else if (board[x, row] == Token.Default)
						return -1;
				}
			}
			return -1;
		}

		private int CheckUp(Token[,] board, int column, int row)
		{
			if (row != 0
				&& board[column, row - 1] != activePlayer.Token
				&& board[column, row - 1] != Token.Default)
			{
				for (int x = row - 2; x >= 0; x--)
				{
					if (board[column, x] == activePlayer.Token)
						return x;
					else if (board[column, x] == Token.Default)
						return -1;
				}
			}
			return -1;
		}

		private int CheckDown(Token[,] board, int column, int row)
		{
			if (row != board.GetLength(1) - 1
				&& board[column, row + 1] != activePlayer.Token
				&& board[column, row + 1] != Token.Default)
			{
				for (int x = row + 2; x < board.GetLength(1); x++)
				{
					if (board[column, x] == activePlayer.Token)
						return x;
					else if (board[column, x] == Token.Default)
						return -1;
				}
			}
			return -1;
		}

		private int CheckRightDown(Token[,] board, int column, int row)
		{
			if (column != board.GetLength(0) - 1 && row != board.GetLength(1) - 1
				&& board[column + 1, row + 1] != activePlayer.Token
				&& board[column + 1, row + 1] != Token.Default)
			{
				for (int x = column + 2, y = row + 2; x < board.GetLength(0) && y < board.GetLength(1); x++, y++)
				{
					if (board[x, y] == activePlayer.Token)
						return x;
					else if (board[x, y] == Token.Default)
						return -1;
				}
			}
			return -1;
		}

		private int CheckLeftDown(Token[,] board, int column, int row)
		{
			if (column != 0 && row != board.GetLength(1) - 1
				&& board[column - 1, row + 1] != activePlayer.Token
				&& board[column - 1, row + 1] != Token.Default)
			{
				for (int x = column - 2, y = row + 2; x >= 0 && y < board.GetLength(1); x--, y++)
				{
					if (board[x, y] == activePlayer.Token)
						return x;
					else if (board[x, y] == Token.Default)
						return -1;
				}
			}
			return -1;
		}

		private int CheckRightUp(Token[,] board, int column, int row)
		{
			if (column != board.GetLength(0) - 1 && row != 0
				&& board[column + 1, row - 1] != activePlayer.Token
				&& board[column + 1, row - 1] != Token.Default)
			{
				for (int x = column + 2, y = row - 2; y >= 0 && x < board.GetLength(0); x++, y--)
				{
					if (board[x, y] == activePlayer.Token)
						return x;
					else if (board[x, y] == Token.Default)
						return -1;
				}
			}
			return -1;
		}

		private int CheckLeftUp(Token[,] board, int column, int row)
		{
			if (column != 0 && row != 0
				&& board[column - 1, row - 1] != activePlayer.Token
				&& board[column - 1, row - 1] != Token.Default)
			{
				for (int x = column - 2, y = row - 2; x >= 0 && y >= 0; x--, y--)
				{
					if (board[x, y] == activePlayer.Token)
						return x;
					else if (board[x, y] == Token.Default)
						return -1;
				}
			}
			return -1;
		}

		#endregion

		#region EndTurn

		private bool isLastMoveSkipped = false;

		private void ChangePlayer()
		{
			if (activePlayer == this.player1)
			{
				this.activePlayer = this.player2;
			}
			else
			{
				this.activePlayer = this.player1;
			}

			HandleMove();
		}

		private void HandleMove()
		{
			this.possibleMoves = GetPossibleMoves();

			if (possibleMoves.Length > 0)
			{
				int player = this.activePlayer == player1 ? 1 : 2;
				if (isLastMoveSkipped)
				{
					int skipped = player == 1 ? 2 : 1;
					OnStatusChanged(string.Format("Spieler {0} muss aussetzen, weil er keinen Stein setzen kann.", skipped),
						string.Format("Spieler {0} ist an der Reihe", player));
				}
				else
				{
					OnStatusChanged("Spiel läuft", string.Format("Spieler {0} ist an der Reihe", player));
				}
				isLastMoveSkipped = false;
				this.activePlayer.StartMove(ref this.possibleMoves);
			}
			else if (!isLastMoveSkipped)
			{
				SkipMove();
			}
			else
			{
				EndGame();
			}
		}

		private void SkipMove()
		{
			isLastMoveSkipped = true;
			ChangePlayer();
		}

		private void EndGame()
		{
			isRunning = false;
			isLastMoveSkipped = false;
			int tokens1;
			int tokens2;
			int winner = GetWinner(out tokens1, out tokens2);

			int tokensWinner = tokens1 > tokens2 ? tokens1 : tokens2;
			int tokensLoser = tokens1 < tokens2 ? tokens1 : tokens2;


			if (winner > 0)
			{
				OnStatusChanged(string.Format("Spiel beendet. Spieler {0} gewinnt mit {1} - {2}.",
					winner, tokensWinner, tokensLoser), string.Empty);
				OnWinnerFound(winner);
			}
			else
			{
				OnStatusChanged(string.Format("Spiel beendet. Das Spiel endete unentschieden {0} - {1}.",
					tokens1, tokens2), string.Empty);
			}
			OnGameEnded(winner, tokens1, tokens2);
		}

		private int GetWinner(out int countTokens1, out int countTokens2)
		{
			countTokens1 = 0;
			countTokens2 = 0;
			foreach (Token stein in board)
			{
				if (stein == Token.Player1)
				{
					countTokens1++;
				}
				else if (stein == Token.Player2)
				{
					countTokens2++;
				}
			}
			if (countTokens1 > countTokens2)
			{
				return 1;
			}
			else if (countTokens2 > countTokens1)
			{
				return 2;
			}
			else
			{
				return 0;
			}
		}

		#endregion

		#endregion

		#region EventHandler
		#region TokenPlaced
		public delegate void TokenPlacedEventHandler(object sender, TokenPlacedEventArgs e);

		public event TokenPlacedEventHandler TokenPlaced;

		protected void OnTokenPlaced(Index index)
		{
			if (TokenPlaced != null)
			{
				TokenPlacedEventArgs args = new TokenPlacedEventArgs();
				args.Board = this.board;
				args.Index = index;
				TokenPlaced(this, args);
			}
		}
		#endregion

		#region StatusChanged
		public delegate void StatusChangedEventHandler(object sender, StatusChangedEventArgs e);

		public event StatusChangedEventHandler StatusChanged;

		protected void OnStatusChanged(string status, string player)
		{
			if (StatusChanged != null)
			{
				StatusChangedEventArgs args = new StatusChangedEventArgs();
				args.Status = status;
				args.Player = player;

				StatusChanged(this, args);
			}
		}

		#endregion

		#region GameEnded
		public delegate void GameEndedEventHandler(object sender, GameEndedEventArgs e);

		public event GameEndedEventHandler GameEnded;

		protected void OnGameEnded(int winner, int tokens1, int tokens2)
		{
			if (GameEnded != null)
			{
				GameEndedEventArgs args = new GameEndedEventArgs();
				args.Winner = winner;
				args.Id1 = player1.Id;
				args.Id2 = player2.Id;
				args.CountTokens1 = tokens1;
				args.CountTokens2 = tokens2;
				args.CountPossibleMoves1 = player1.CountPossibleMoves;
				args.CountPossibleMoves2 = player2.CountPossibleMoves;

				for (int i = 0; i < board.GetLength(0); i += 7)
				{
					for (int j = 0; j < board.GetLength(1); j += 8)
					{
						if (board[i, j] == Token.Player1)
						{
							args.CountCorners1++;
						}
						else if (board[i, j] == Token.Player2)
						{
							args.CountCorners2++;
						}
					}
				}

				GameEnded(this, args);
			}
		}

		#endregion

		#region WinnerFound
		public delegate void WinnerFoundEventHandler(object sender, WinnerFoundEventArgs e);

		public event WinnerFoundEventHandler WinnerFound;

		protected void OnWinnerFound(int winner)
		{
			if (WinnerFound != null)
			{
				WinnerFoundEventArgs args = new WinnerFoundEventArgs();
				args.Winner = winner;
				WinnerFound(this, args);
			}
		}

		#endregion

		#endregion

		#region SmartComputer
		public Index GetSmartPosition()
		{
			int value = 0;
			int maxTokenChange = 0;
			int tokenChangeCount = 0;
			Index retIndex = new Index();
			Random rand = new Random();
			for (int column = 0; column < board.GetLength(0); column++)
			{
				for (int row = 0; row < board.GetLength(1); row++)
				{
					if (CheckAll(column, row))
					{
						tokenChangeCount = 0;
						value = CheckRight(board, column, row);
						if (value != -1)
						{
							tokenChangeCount += Math.Abs(column - value);
						}
						value = CheckLeft(board, column, row);
						if (value != -1)
						{
							tokenChangeCount += Math.Abs(column - value);
						}
						value = CheckUp(board, column, row);
						if (value != -1)
						{
							tokenChangeCount += Math.Abs(row - value);
						}
						value = CheckDown(board, column, row);
						if (value != -1)
						{
							tokenChangeCount += Math.Abs(row - value);
						}
						value = CheckLeftUp(board, column, row);
						if (value != -1)
						{
							tokenChangeCount += Math.Abs(column - value);
						}
						value = CheckLeftDown(board, column, row);
						if (value != -1)
						{
							tokenChangeCount += Math.Abs(column - value);
						}
						value = CheckRightUp(board, column, row);
						if (value != -1)
						{
							tokenChangeCount += Math.Abs(column - value);
						}
						value = CheckRightDown(board, column, row);
						if (value != -1)
						{
							tokenChangeCount += Math.Abs(column - value);
						}
						//Ecken sollten immer sofort besetzt werden
						if (isCorner(column, row))
						{
							return new Index(column, row);
						}
						//Randfelder, die nicht "gefährlich sind" sollten bevorzugt werden
						// Sie bekommen einen Bonus von + 3
						if (isBoundaryField(column, row) && !isDangerousPosition(column, row) && tokenChangeCount + 3 > maxTokenChange)
						{
							maxTokenChange = tokenChangeCount + 3;
							retIndex.Column = column;
							retIndex.Row = row;
						}
						//eine gefährliche Position sollte vernachlässigt werden, deshalb bekommt sie einen Abzug von -5
						else if (isDangerousPosition(column, row) && tokenChangeCount - 5 > maxTokenChange)
						{
							maxTokenChange = tokenChangeCount - 5;
							retIndex.Column = column;
							retIndex.Row = row;
						}
						//eine Position, die mehr neue Punkte bringt sollte bevorzugt werden
						else if (tokenChangeCount >= maxTokenChange)
						{
							Random random = new Random();
							if (random.Next(100) >= 50)
							{
								maxTokenChange = tokenChangeCount;
								retIndex.Column = column;
								retIndex.Row = row;
							}
						}
						//Es sollte am Anfang das Erstbeste Token genommen werden, damit überhaupt etwas zurück gegeben wird
						if (retIndex.Row == -1)
						{
							maxTokenChange = tokenChangeCount;
							retIndex.Column = column;
							retIndex.Row = row;
						}
					}
				}
			}
			return retIndex;
		}
		private bool isBoundaryField(int column, int row)
		{
			return (column == 0 || column == board.GetLength(0) - 1 || row == 0 || row == board.GetLength(0) - 1);
		}
		private bool isCorner(int column, int row)
		{
			return (column == 0 && row == 0) || (column == 0 && row == board.GetLength(1) - 1)
							|| (column == board.GetLength(0) - 1 && row == 0)
							|| (column == board.GetLength(0) - 1 && row == board.GetLength(1) - 1);
		}
		//Positionen, die an eine Ecke Grenzen,die noch nicht besetzt sind "gefährliche" Positionen
		private bool isDangerousPosition(int column, int row)
		{
			if ((column == 0 && row == 1) || (column == 1 && row == 0) || (column == 1 && row == 1) && board[0, 0] == Token.Default)
				return true;
			if ((column == 6 && row == 0) || (column == 6 && row == 1) || (column == 7 && row == 1) && board[7, 0] == Token.Default)
				return true;
			if ((column == 0 && row == 6) || (column == 1 && row == 6) || (column == 1 && row == 7) && board[0, 7] == Token.Default)
				return true;
			if ((column == 6 && row == 6) || (column == 6 && row == 7) || (column == 7 && row == 6) && board[7, 7] == Token.Default)
				return true;

			return false;
		}
		#endregion

	}
}

